
#include <time.h>
#include <string>

#include <Qt>
#include <QApplication>
#include <QDateTime>
#include <QTimer>
#include <QtConcurrent/QtConcurrent>

#include "proc.h"
#include "gui_utils.h"
#include "treemodel.h"

using namespace std;

static const int FLUSH_SPEED_HIGH   = 500;      // 较高刷新速度 （每500ms刷新一次）
static const int FLUSH_SPEED_NORM   = 1000;     // 普通刷新速度 （每1s刷新一次）
static const int FLUSH_SPEED_SLOW   = 2000;     // 较低刷新速度 （每2s刷新一次）

static const int DEFAULT_FLUSH_SPEED = FLUSH_SPEED_SLOW; // 默认刷新速度采用较慢值

TreeModel::TreeModel(TaskTree *taskTree, QObject *parent)
    : QAbstractItemModel(parent), m_tasktree(taskTree)
{
    m_timer = new QTimer(this);
    m_timer->setInterval(DEFAULT_FLUSH_SPEED);
    connect(m_timer, SIGNAL(timeout()), this, SLOT(flush()));
    m_timer->start();

    m_threadpool = new QThreadPool(this);
    m_threadpool->setMaxThreadCount(1);
}

TreeModel::~TreeModel() {}

int TreeModel::columnCount(const QModelIndex &) const
{
    // 比属性数量多返回一列，多出来的这一列在最后，什么都不显示，用于占位
    // 如果没有占位列，显示在最后一列的属性列调整宽度时用户体验不好
    return TIPUtils::getPropCount() + 1;
}

static inline double byte_to_kb(uintptr_t size)
{
    return size / double(KB);
}

static inline double byte_to_mb(uintptr_t size)
{
    return size / double(MB);
}

static inline double byte_to_gb(uintptr_t size)
{
    return size / double(GB);
}

static QString byte_to_human(uintptr_t size)
{
    const char *FMT = "%.1lf%s";
    if (size > GB) {
        return QString::asprintf(FMT, byte_to_gb(size), "GB");
    }
    if (size > MB) {
        return QString::asprintf(FMT, byte_to_mb(size), "MB");
    }
    if (size > KB) {
        return QString::asprintf(FMT, byte_to_kb(size), "KB");
    }
    return QString::number(size);
}

static inline QString cpu_usage_to_human(double cpu_usage)
{
    return QString::asprintf("%.1lf", cpu_usage * 100.0);
}

static QString get_prop_str_value(TIP prop, const TaskInfo &info)
{
    switch (prop) {
    case TIP::PID:
        return QString::number(info.getPid());
    case TIP::PPID:
        return QString::number(info.getPPid());
    case TIP::NAME:
        return QString::fromStdString(info.getName());
    case TIP::USERNAME:
        return QString::fromStdString(info.getUserName());
    case TIP::STATE:
        return GUIUtils::stateToString(info.getState());
    case TIP::VM_SIZE:
        return byte_to_human(info.getVmSize());
    case TIP::VM_RESIDENT:
        return byte_to_human(info.getVmResident());
    case TIP::VM_SHARED:
        return byte_to_human(info.getVmShared());
    case TIP::VM_PRIVATE:
        return byte_to_human(info.getVmPrivate());
    case TIP::CPU_USAGE:
        return cpu_usage_to_human(info.getCpuUsage());
    case TIP::CPU_TIME:
        return QString::number(info.getCpuTime());
    case TIP::START_TIME:
        return QDateTime::fromTime_t(info.getStartTime()).toString();
    case TIP::NICE:
        return QString::number(info.getNice());
    case TIP::CMD:
        return QString::fromStdString(info.getCmd());
    default:
        return QString();
    }
}

Qt::AlignmentFlag get_align(TIP prop)
{
    switch (prop) {
    case TIP::PID:
    case TIP::VM_SIZE:
    case TIP::VM_RESIDENT:
    case TIP::VM_SHARED:
    case TIP::VM_PRIVATE:
    case TIP::CPU_TIME:
    case TIP::CPU_USAGE:
    case TIP::NICE:
        return Qt::AlignRight;

    default:
        return Qt::AlignLeft;
    }
}

QVariant TreeModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (role == Qt::TextAlignmentRole) {
        return get_align(TIPUtils::getPropAt(index.column()));
    }

    if (role == Qt::DisplayRole) {

        pid_t pid = pid_t(index.internalId());
        if (pid > 0 && m_tasktree->contains(pid)) {
            TaskInfo info;
            m_tasktree->info(pid, &info);
            TIP prop = TIPUtils::getPropAt(index.column());
            return get_prop_str_value(prop, info);
        }

    }

    return QVariant();
}

Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
    if (index.isValid()) {
        return QAbstractItemModel::flags(index);
    }
    return 0;
}

QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
        if (section < TIPUtils::getPropCount()) {
            return GUIUtils::propToString(TIPUtils::getPropAt(section));
        }
        return QString(); // 占位列
    }
    return QVariant();
}

QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
{
    if (!hasIndex(row, column, parent))
        return QModelIndex();

    pid_t pid;

    if (!parent.isValid()) {
        pid = m_tasktree->getRootAt(row);
    } else {
        pid_t ppid = pid_t(parent.internalId());
        pid = m_tasktree->getChildAt(ppid, row);
    }

    if (pid > 0) {
        return createIndex(row, column, pid);
    } else {
        return QModelIndex();
    }
}

QModelIndex TreeModel::parent(const QModelIndex &index) const
{
    if (!index.isValid()) {
        return QModelIndex();
    }

    if (!m_tasktree->isTreeMode()) {
        return QModelIndex();
    }

    pid_t pid = pid_t(index.internalId());
    pid_t ppid = m_tasktree->getParent(pid);
    if (ppid <= 0) {
        return QModelIndex();
    }
    int row = m_tasktree->getIndex(ppid);
    if (row < 0) {
        return QModelIndex();
    }

    return createIndex(row, 0, ppid);
}

int TreeModel::rowCount(const QModelIndex &parent) const
{
    if (parent.column() > 0) {
        return 0;
    }

    if (!parent.isValid()) {
        return m_tasktree->getRootCount();
    } else {
        pid_t ppid = pid_t(parent.internalId());
        return m_tasktree->getChildCount(ppid);
    }

}

enum class TreeEventType
{
    AFTER_FLUSH,
    AFTER_SORT,
    AFTER_CHANGE_TREE_MODE,
};

class TreeEvent : public QEvent
{
public:
    TreeEvent(TreeEventType type)
        : QEvent(QEvent::User),
          m_type(type) {}

    TreeEventType getType() const { return m_type; }

    static void post(QObject *receiever, TreeEventType type)
    {
        QApplication::postEvent(receiever, new TreeEvent(type));
    }

private:
    const TreeEventType m_type;
};

void TreeModel::flush() // slot
{
    QtConcurrent::run(m_threadpool, [this]{
        m_tasktree->flush();
        TreeEvent::post(this, TreeEventType::AFTER_FLUSH);
    });
}

void TreeModel::setFlushSpeed(FlushSpeed speed)
{
    switch (speed)
    {
    case FlushSpeed::High:
        m_timer->setInterval(FLUSH_SPEED_HIGH);
        return;
    case FlushSpeed::Normal:
        m_timer->setInterval(FLUSH_SPEED_NORM);
        return;
    case FlushSpeed::Slow:
        m_timer->setInterval(FLUSH_SPEED_SLOW);
        return;
    }
}

void TreeModel::setTreeMode(bool treeMode) // slot
{
    QtConcurrent::run(m_threadpool, [this, treeMode]{
        if (m_tasktree->setTreeMode(treeMode)) {
            TreeEvent::post(this, TreeEventType::AFTER_CHANGE_TREE_MODE);
        }
    });
}

void TreeModel::setAutoFlush(bool autoFlush)
{
    if (autoFlush) {
        if (!m_timer->isActive()) {
            m_timer->start();
        }
    } else {
        if (m_timer->isActive()) {
            m_timer->stop();
        }
    }
}

bool TreeModel::isAutoFlush() const
{
    return m_timer->isActive();
}

QModelIndex TreeModel::getIndexByPid(pid_t pid) const
{
    if (pid <= 0) {
        return QModelIndex();
    }

    int row = m_tasktree->getIndex(pid);
    if (row >= 0) {
        return createIndex(row, 0, pid);
    }
    return QModelIndex();

}

void TreeModel::sort(int column, Qt::SortOrder order)
{
    if (column >= 0 && column < TIPUtils::getPropCount()) {

        m_sort_column = column;
        m_sort_order = order;

        emit beforeSort();

        QtConcurrent::run(m_threadpool, [this, column, order]{
            if (order == Qt::AscendingOrder) {
                m_tasktree->sort(TIPUtils::getPropAt(column), SortOrder::ASC);
            } else {
                m_tasktree->sort(TIPUtils::getPropAt(column), SortOrder::DESC);
            }
            TreeEvent::post(this, TreeEventType::AFTER_SORT);
        });

    }
}

void TreeModel::_remove_items()
{
    vector<Location> locs;
    m_tasktree->getRemoved(&locs);

    if (m_tasktree->isTreeMode()) {

        std::sort(locs.begin(), locs.end(), [](const Location &a, const Location &b){
            return a.getIndex() > b.getIndex();
        });

        for (const Location &loc : locs) {
            QModelIndex index = getIndexByPid(loc.getPPid());
            if (index.isValid()) {
                beginRemoveRows(getIndexByPid(loc.getPPid()), loc.getIndex(), loc.getIndex());
                endRemoveRows();
            }
        }

    } else {

        std::sort(locs.begin(), locs.end(), [](const Location &a, const Location &b){
            pid_t ppid_a = a.getPPid();
            pid_t ppid_b = b.getPPid();
            if (ppid_a > ppid_b) {
                return true;
            }
            if (ppid_a < ppid_b) {
                return false;
            }
            return a.getIndex() > b.getIndex();
        });

        for (const Location &loc : locs) {
            beginRemoveRows(QModelIndex(), loc.getIndex(), loc.getIndex());
            endRemoveRows();
        }

    }

}

void TreeModel::_insert_new_items()
{
    vector<Location> locs;
    m_tasktree->getInserted(&locs);

    if (m_tasktree->isTreeMode()) {

        std::sort(locs.begin(), locs.end(), [](const Location &a, const Location &b){
            return a.getIndex() < b.getIndex();
        });

        for (const Location &loc : locs) {
            QModelIndex index = getIndexByPid(loc.getPPid());
            if (index.isValid()) {
                beginInsertRows(index, loc.getIndex(), loc.getIndex());
                endInsertRows();
            }
        }

    } else {

        std::sort(locs.begin(), locs.end(), [](const Location &a, const Location &b){
            pid_t ppid_a = a.getPPid();
            pid_t ppid_b = b.getPPid();
            if (ppid_a < ppid_b) {
                return true;
            }
            if (ppid_a > ppid_b) {
                return false;
            }
            return a.getIndex() < b.getIndex();
        });

        for (const Location &loc : locs) {
            beginInsertRows(QModelIndex(), loc.getIndex(), loc.getIndex());
            endInsertRows();
        }

    }

}

bool TreeModel::event(QEvent *e)
{
    if (e->type() == QEvent::User) {
        TreeEvent *te = (TreeEvent *) e;
        switch (te->getType()) {
        case TreeEventType::AFTER_FLUSH:
        {
            _remove_items();

            // 插入新项
            _insert_new_items();

            // 刷新任务
            // 要注意一定要把刷新操作放在所有插入和删除和移动操作之后
            dataChanged(index(0, 0), index(m_tasktree->getRootCount() - 1, TIPUtils::getPropCount() - 1));
            break;
        }
        case TreeEventType::AFTER_SORT:
        {
            layoutAboutToBeChanged();
            layoutChanged();

            emit afterSort();

            break;
        }
        case TreeEventType::AFTER_CHANGE_TREE_MODE:
        {
            beginResetModel();
            endResetModel();
            break;
        }
        default:
            break;
        }
    }

    return QAbstractItemModel::event(e);
}
